package com.gigalaxy.game.card.phom.utils
{
	Array.prototype.removeByValue = function(val)
	{
		for (var i = 0; i < this.length; i++)
		{
			if (this[i] == val)
			{
				this.splice(i, 1);
				break;
			}
		}
		return this;
	};

	Array.prototype.addAll = function(anotherArr)
	{
		for (var i = 0; i < anotherArr.length; i++)
		{
			this.push(anotherArr[i]);
		}
		return this;
	};

	Array.prototype.removeAll = function(anotherArr)
	{
		for (var i = 0; i < anotherArr.length; i++)
		{
			this.removeByValue(anotherArr[i]);
		}
		return this;
	};

	function powerset(arr)
	{
		var ps = new Array(new Array());
		for (var i = 0; i < arr.length; i++)
		{
			// we modify the ps array in the next loop,
			// so can't use the ps.length property directly in the loop condition.
			var current_length = ps.length;
			for (var j = 0; j < current_length; j++)
			{
				ps.push(ps[j].concat(arr[i]));
			}
		}
		return ps;
	}
	//*************************************************************************************************************//
	//*************************************************************************************************************//
	//*************************************************************************************************************//

	var SUIT = {};
	SUIT.SPADES = "♠";
	SUIT.CLUBS = "♣";
	SUIT.DIAMONDS = "♦";
	SUIT.HEARTS = "♥";

	function Card(rank, suit)
	{

		this.getRank = function()
		{
			return rank;
		};

		this.getSuit = function()
		{
			return suit;
		};

		this.toString = function()
		{
			var rankStr;
			switch (this.getRank())
			{
				case 1:
					rankStr = "A";
					break;
				case 11:
					rankStr = "J";
					break;
				case 12:
					rankStr = "Q";
					break;
				case 13:
					rankStr = "K";
					break;
				default:
					rankStr = this.getRank();
					break;
			}
			return rankStr + this.getSuit();
		};
	}

	Card.SPADES_A = new Card(1, "♠");
	Card.SPADES_2 = new Card(2, "♠");
	Card.SPADES_3 = new Card(3, "♠");
	Card.SPADES_4 = new Card(4, "♠");
	Card.SPADES_5 = new Card(5, "♠");
	Card.SPADES_6 = new Card(6, "♠");
	Card.SPADES_7 = new Card(7, "♠");
	Card.SPADES_8 = new Card(8, "♠");
	Card.SPADES_9 = new Card(9, "♠");
	Card.SPADES_10 = new Card(10, "♠");
	Card.SPADES_J = new Card(11, "♠");
	Card.SPADES_Q = new Card(12, "♠");
	Card.SPADES_K = new Card(13, "♠");

	Card.CLUBS_A = new Card(1, "♣");
	Card.CLUBS_2 = new Card(2, "♣");
	Card.CLUBS_3 = new Card(3, "♣");
	Card.CLUBS_4 = new Card(4, "♣");
	Card.CLUBS_5 = new Card(5, "♣");
	Card.CLUBS_6 = new Card(6, "♣");
	Card.CLUBS_7 = new Card(7, "♣");
	Card.CLUBS_8 = new Card(8, "♣");
	Card.CLUBS_9 = new Card(9, "♣");
	Card.CLUBS_10 = new Card(10, "♣");
	Card.CLUBS_J = new Card(11, "♣");
	Card.CLUBS_Q = new Card(12, "♣");
	Card.CLUBS_K = new Card(13, "♣");

	Card.DIAMONDS_A = new Card(1, "♦");
	Card.DIAMONDS_2 = new Card(2, "♦");
	Card.DIAMONDS_3 = new Card(3, "♦");
	Card.DIAMONDS_4 = new Card(4, "♦");
	Card.DIAMONDS_5 = new Card(5, "♦");
	Card.DIAMONDS_6 = new Card(6, "♦");
	Card.DIAMONDS_7 = new Card(7, "♦");
	Card.DIAMONDS_8 = new Card(8, "♦");
	Card.DIAMONDS_9 = new Card(9, "♦");
	Card.DIAMONDS_10 = new Card(10, "♦");
	Card.DIAMONDS_J = new Card(11, "♦");
	Card.DIAMONDS_Q = new Card(12, "♦");
	Card.DIAMONDS_K = new Card(13, "♦");

	Card.HEARTS_A = new Card(1, "♥");
	Card.HEARTS_2 = new Card(2, "♥");
	Card.HEARTS_3 = new Card(3, "♥");
	Card.HEARTS_4 = new Card(4, "♥");
	Card.HEARTS_5 = new Card(5, "♥");
	Card.HEARTS_6 = new Card(6, "♥");
	Card.HEARTS_7 = new Card(7, "♥");
	Card.HEARTS_8 = new Card(8, "♥");
	Card.HEARTS_9 = new Card(9, "♥");
	Card.HEARTS_10 = new Card(10, "♥");
	Card.HEARTS_J = new Card(11, "♥");
	Card.HEARTS_Q = new Card(12, "♥");
	Card.HEARTS_K = new Card(13, "♥");

	Card.cardsBySuit = {"♠": [Card.SPADES_A, Card.SPADES_2, Card.SPADES_3, Card.SPADES_4, Card.SPADES_5, Card.SPADES_6, Card.SPADES_7, Card.SPADES_8, Card.SPADES_9, Card.SPADES_10, Card.SPADES_J, Card.SPADES_Q, Card.SPADES_K], "♣": [Card.CLUBS_A, Card.CLUBS_2, Card.CLUBS_3, Card.CLUBS_4, Card.CLUBS_5, Card.CLUBS_6, Card.CLUBS_7, Card.CLUBS_8, Card.CLUBS_9, Card.CLUBS_10, Card.CLUBS_J, Card.CLUBS_Q, Card.CLUBS_K], "♦": [Card.DIAMONDS_A, Card.DIAMONDS_2, Card.DIAMONDS_3, Card.DIAMONDS_4, Card.DIAMONDS_5, Card.DIAMONDS_6, Card.DIAMONDS_7, Card.DIAMONDS_8, Card.DIAMONDS_9, Card.DIAMONDS_10, Card.DIAMONDS_J, Card.DIAMONDS_Q, Card.DIAMONDS_K], "♥": [Card.HEARTS_A, Card.HEARTS_2, Card.HEARTS_3, Card.HEARTS_4, Card.HEARTS_5, Card.HEARTS_6, Card.HEARTS_7, Card.HEARTS_8, Card.HEARTS_9, Card.HEARTS_10, Card.HEARTS_J, Card.HEARTS_Q, Card.HEARTS_K]};
	Card.suits = ["♠", "♣", "♦", "♥"];
	Card.rankStrings = {"A": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "10": 10, "J": 11, "Q": 12, "K": 13};
	Card.cardOf = function(rank, suit)
	{
		if (Card.suits.indexOf(suit) == -1)
		{
			throw "Invalid suit " + suit;
		}
		if (rank < 1 || rank > 13)
		{
			throw "Invalid rank " + rank;
		}

		var cardsWithSuit = Card.cardsBySuit[suit];
		for (var i = 0; i < cardsWithSuit.length; i++)
		{
			if (cardsWithSuit[i].getRank() == rank)
			{
				return cardsWithSuit[i];
			}
		}
	};

	Card.RANK_COMPARATOR = function(card1, card2)
	{
		return card1.getRank() - card2.getRank();
	};


	Card.decodeString = function(str)
	{
		if (str === "")
		{
			return [];
		}

		var cardsStrs = str.trim().split(" ");
		var result = [];
		for (var i = 0; i < cardsStrs.length; i++)
		{
			var suitStr = cardsStrs[i].substring(cardsStrs[i].length - 1);
			var rankStr = cardsStrs[i].substring(0, cardsStrs[i].length - 1);
			result.push(Card.cardOf(Card.rankStrings[rankStr], suitStr));
		}
		return result;
	};
	Card.encodeString = function(cards)
	{
		var str = "";
		for (var i = 0; i < cards.length; i++)
		{
			str += cards[i].toString() + " ";
		}
		return str.trim();
	};

	var CONST = {};
	CONST.minNumOfCardInPhom = 3;

	function Phom(cards, type)
	{

		var rank = 0;
		for (var i = 0; i < cards.length; i++)
		{
			rank += cards[i].getRank();
		}

		if (cards.length < CONST.minNumOfCardInPhom)
		{
			throw "Invalid phom " + cards.toString();
		}
		this.size = function()
		{
			return cards.length;
		};

		this.rank = function()
		{
			return rank;
		};

		this.getCards = function()
		{
			return cards;
		};

		this.getType = function()
		{
			return type;
		};

		this.intersec = function(anotherPhom)
		{
			return Utils.intersection(this.getCards(), anotherPhom.getCards());
		};

		this.toString = function()
		{
			return "Phom[" + cards.toString() + "]";
		};

		this.buildAllPossibleNonIntersec = function(intersecPoints, takenCards)
		{
			if (this.size() > CONST.minNumOfCardInPhom)
			{
				return this.getType().buildAllPossibleNonInterec(this, intersecPoints, takenCards);
			}
			else
			{
				var result = new Array();
				result.push(this);
				return result;
			}
		}
	}
	var STRAIGHT = {};
	STRAIGHT.getBestPhom = function(cards)
	{
		var cardsBySuit = {};
		for (var i = 0; i < cards.length; i++)
		{
			if (cardsBySuit[cards[i].getSuit()] == undefined)
			{
				cardsBySuit[cards[i].getSuit()] = new Array();
			}
			cardsBySuit[cards[i].getSuit()].push(cards[i]);
		}

		var result = new Array();
		for (key in cardsBySuit)
		{
			if (cardsBySuit[key].length < CONST.minNumOfCardInPhom)
				continue;
			cardsBySuit[key].sort(Card.RANK_COMPARATOR);
			var firstCard = cardsBySuit[key][0];
			var straightCards = new Array();
			straightCards.push(firstCard);
			for (i = 1; i < cardsBySuit[key].length; i++)
			{
				if (cardsBySuit[key][i].getRank() != firstCard.getRank() + 1)
				{
					if (straightCards.length >= CONST.minNumOfCardInPhom)
					{
						result.push(new Phom(straightCards, STRAIGHT));
					}
					straightCards = new Array();
				}

				straightCards.push(cardsBySuit[key][i]);
				firstCard = cardsBySuit[key][i];
			}
			if (straightCards.length >= CONST.minNumOfCardInPhom)
			{
				result.push(new Phom(straightCards, STRAIGHT));
			}
		}

		return result;
	};
	STRAIGHT.buildAllPossibleNonInterec = function(phom, cardsIntersecPoint, takenCards)
	{
		var realIntersec = Utils.intersection(phom.getCards(), cardsIntersecPoint);
		var takenIntersec = Utils.intersection(phom.getCards(), takenCards);

		if (takenIntersec.length > 0)
		{
			var allPossiblePhomsWithTakenCards = Utils.allPossibleWithTakenCards(phom, takenIntersec, STRAIGHT);
			var result = new Array();
			for (var i = 0; i < allPossiblePhomsWithTakenCards.length; i++)
			{
				var withTakenPhom1 = allPossiblePhomsWithTakenCards[i];
				var localIntersec = Utils.intersection(withTakenPhom1.getCards(), realIntersec);

				for (var j = 0; j < allPossiblePhomsWithTakenCards.length; j++)
				{
					if (i != j)
					{
						var withTakenPhom2 = allPossiblePhomsWithTakenCards[j];
						localIntersec.addAll(withTakenPhom1.intersec(withTakenPhom2));
					}
				}
				result.addAll(Utils.allPossibleNonIntersec(withTakenPhom1, localIntersec, STRAIGHT));
			}
			return result;
		}
		else
		{
			return Utils.allPossibleNonIntersec(phom, realIntersec, STRAIGHT);
		}
	};
	STRAIGHT.bestInclude = function(cards, card)
	{
		var sameSuitCards = new Array();
		sameSuitCards.push(card);
		for (var i = 0; i < cards.length; i++)
		{
			if (cards[i].getSuit() === card.getSuit())
			{
				sameSuitCards.push(cards[i]);
			}
		}

		var phoms = STRAIGHT.getBestPhom(sameSuitCards);
		if (phoms.length == 1)
		{
			return phoms[0];
		}

		return null;
	};

	var SAME_RANK = {};
	SAME_RANK.getBestPhom = function(cards)
	{
		var cardsRankMap = {};
		for (var i = 0; i < cards.length; i++)
		{
			if (cardsRankMap[cards[i].getRank()] == undefined)
			{
				cardsRankMap[cards[i].getRank()] = new Array();
			}
			cardsRankMap[cards[i].getRank()].push(cards[i]);
		}

		var result = new Array();
		for (key in cardsRankMap)
		{
			if (cardsRankMap[key].length < CONST.minNumOfCardInPhom)
			{
				continue;
			}
			result.push(new Phom(cardsRankMap[key], SAME_RANK));
		}

		return result;
	};
	SAME_RANK.buildAllPossibleNonInterec = function(phom, cardsIntersecPoint, takenCards)
	{
		var realIntersec = Utils.intersection(phom.getCards(), cardsIntersecPoint);
		var takenIntersec = Utils.intersection(phom.getCards(), takenCards);

		if (takenIntersec.length > 0)
		{
			var allPossiblePhomsWithTakenCards = Utils.allPossibleWithTakenCards(phom, takenIntersec, SAME_RANK);
			var result = new Array();
			for (var i = 0; i < allPossiblePhomsWithTakenCards.length; i++)
			{
				var withTakenPhom1 = allPossiblePhomsWithTakenCards[i];
				var localIntersec = Utils.intersection(withTakenPhom1.getCards(), realIntersec);

				for (var j = 0; j < allPossiblePhomsWithTakenCards.length; j++)
				{
					if (i != j)
					{
						var withTakenPhom2 = allPossiblePhomsWithTakenCards[j];
						localIntersec.addAll(withTakenPhom1.intersec(withTakenPhom2));
					}
				}
				result.addAll(Utils.allPossibleNonIntersec(withTakenPhom1, localIntersec, SAME_RANK));
			}
			return result;
		}
		else
		{
			return Utils.allPossibleNonIntersec(phom, realIntersec, SAME_RANK);
		}
	};
	SAME_RANK.bestInclude = function(cards, card)
	{
		var sameRankCards = new Array();
		sameRankCards.push(card);
		for (var i = 0; i < cards.length; i++)
		{
			if (cards[i].getRank() == card.getRank())
			{
				sameRankCards.push(cards[i]);
			}
		}

		var phoms = SAME_RANK.getBestPhom(sameRankCards);
		if (phoms.length == 1)
		{
			return phoms[0];
		}
		return null;
	};

	var Utils = {};
	Utils.allPossibleNonIntersec = function(phom, intersecCards, sameRankOrStraight)
	{
		var result = new Array();
		var powerSetIntersecCards = powerset(intersecCards);
		for (var i = 0; i < powerSetIntersecCards.length; i++)
		{
			var phomCopyCards = phom.getCards().slice(0);
			phomCopyCards.removeAll(powerSetIntersecCards[i]);
			result.addAll(sameRankOrStraight.getBestPhom(phomCopyCards));
		}

		return result;
	};
	Utils.allPossibleWithTakenCards = function(phom, takenCards, sameRankOrStraight)
	{
		var phomCards = phom.getCards().slice(0);
		phomCards.removeAll(takenCards);
		var result = new Array();
		for (var i = 0; i < takenCards.length; i++)
		{
			var bestPhomIncludeTakenCard = sameRankOrStraight.bestInclude(phomCards, takenCards[i]);
			if (bestPhomIncludeTakenCard != null)
			{
				result.push(bestPhomIncludeTakenCard);
			}
		}
		return result;
	};
	Utils.intersection = function(arr1, arr2)
	{
		var result = new Array();
		for (var i = 0; i < arr1.length; i++)
		{
			if (arr2.indexOf(arr1[i]) != -1)
			{
				result.push(arr1[i]);
			}
		}
		return result;
	};

	function buildAllPossibleCombinePhoms(cards, takenCards)
	{
		var allSameRank = SAME_RANK.getBestPhom(cards);
		var allStraight = STRAIGHT.getBestPhom(cards);

		var intersecCardsMap = {};
		var phomsToBuild = new Array();
		phomsToBuild.addAll(allSameRank);
		phomsToBuild.addAll(allStraight);

		for (var i = 0; i < allStraight.length; i++)
		{
			intersecCardsMap[allStraight[i]] = new CardSet();
		}

		for (var j = 0; j < allSameRank.length; j++)
		{
			intersecCardsMap[allSameRank[j]] = new CardSet();
		}

		for (i = 0; i < allStraight.length; i++)
		{
			for (j = 0; j < allSameRank.length; j++)
			{
				var intersec = allStraight[i].intersec(allSameRank[j]);
				if (intersec.length > 0)
				{
					intersecCardsMap[allStraight[i]].addAll(intersec);
					intersecCardsMap[allSameRank[j]].addAll(intersec);
				}
			}
		}

		var allPossible = new Array();
		for (i = 0; i < phomsToBuild.length; i++)
		{
			var intesec = intersecCardsMap[phomsToBuild[i]].values();
			allPossible.addAll(phomsToBuild[i].buildAllPossibleNonIntersec(intesec, takenCards));
		}

		var filteredAllPossible = [];
		for (i = 0; i < allPossible.length; i++)
		{
			if (Utils.intersection(allPossible[i].getCards(), takenCards).length < 2)
			{
				filteredAllPossible.push(allPossible[i]);
			}
		}

		var allPossibleCombined = powerset(filteredAllPossible);
		var filteredAllPossibleCombined = [];
		for (i = 0; i < allPossibleCombined.length; i++)
		{
			if (allTakenCardHasPhomSolutionPredicate(allPossibleCombined[i], takenCards))
			{
				filteredAllPossibleCombined.push(allPossibleCombined[i]);
			}
		}
		return filteredAllPossibleCombined;

		function allTakenCardHasPhomSolutionPredicate(phoms, takenCards)
		{
			if (takenCards.length == 0)
				return true;
			var intersectionCardsSet = new CardSet();
			for (var i = 0; i < phoms.length; i++)
			{
				intersectionCardsSet.addAll(Utils.intersection(phoms[i].getCards(), takenCards));
			}
			return intersectionCardsSet.values().length == takenCards.length;
		}
	}

	function bestPhom(cards, takenCards)
	{
		var allPossibleCombine = buildAllPossibleCombinePhoms(cards, takenCards);
		var solutions = [];
		for (var i = 0; i < allPossibleCombine.length; i++)
		{
			if (allPossibleCombine[i].length > 0)
			{
				var solution = new Solution(allPossibleCombine[i]);
				if (solution.hasNoIntersec())
				{
					solutions.push(solution);
				}
			}
		}

		if (solutions.length > 0)
		{
			var min = solutions[0];
			var compareFunc = Solution.INTERSEC$asc_PHOM_SIZE$desc_CARD_RANK$desc_CARD_SIZE$desc_PHOM_TYPE$desc_COMPARATOR;
			for (i = 1; i < solutions.length; i++)
			{
				solution = solutions[i];
				if (compareFunc(solutions[i], min) < 0)
				{
					min = solutions[i];
				}
			}

			return min.getPhoms();
		}

		return [];
	}

	function getPhoms(cardStrs, takenCardStrs)
	{
		var best = bestPhom(Card.decodeString(cardStrs), Card.decodeString(takenCardStrs));
		var result = [];
		for (var i = 0; i < best.length; i++)
		{
			result.push(Card.encodeString(best[i].getCards()));
		}
		return result;
	}

	Solution.INTERSEC$asc_PHOM_SIZE$desc_CARD_RANK$desc_CARD_SIZE$desc_PHOM_TYPE$desc_COMPARATOR = function(o1, o2)
	{
		var compareINTERSEC = o1.getIntersecRank() - o2.getIntersecRank();
		if (compareINTERSEC != 0)
		{
			return compareINTERSEC;
		}

		var comparePHOM_SIZE = o1.getPhomsSize() - o2.getPhomsSize();
		if (comparePHOM_SIZE != 0)
		{
			return -comparePHOM_SIZE;
		}

		var compareCARD_RANK = o1.getTotalCardRank() - o2.getTotalCardRank();
		if (compareCARD_RANK != 0)
		{
			return -compareCARD_RANK;
		}

		var compareCARD_SIZE = o1.getTotalCardSize() - o2.getTotalCardSize();
		if (compareCARD_SIZE != 0)
		{
			return -compareCARD_SIZE;
		}

		var comparePHOM_TYPE = o1.getTotalPhomTypeRank() - o2.getTotalPhomTypeRank();
		if (comparePHOM_TYPE != 0)
		{
			return -comparePHOM_TYPE;
		}

		return 0;
	};

	function Solution(phoms)
	{
		var localIntersec = 0;
		var localTotalSize = 0;
		var localTotalRank = 0;
		var realIntersec = 0;
		var localTotalPhomTypeRank = 0;

		if (phoms.length > 1)
		{
			for (var i = 0; i < phoms.length - 1; i++)
			{
				var p1 = phoms[i];
				for (var j = i + 1; j < phoms.length; j++)
				{
					var p2 = phoms[j];
					var intersecSize = p1.intersec(p2).length;
					realIntersec += intersecSize;
					if (p1.getType() == p2.getType() && p1.getType() == SAME_RANK)
					{
						localIntersec += intersecSize * 9;
					}
					else
					{
						localIntersec += intersecSize;
					}
				}
			}

			for (i = 0; i < phoms.length; i++)
			{
				localTotalSize += phoms[i].size();
				localTotalRank += phoms[i].rank();
				localTotalPhomTypeRank += rankPhomType(phoms[i]);
			}
		}
		else
		{
			var phom = phoms[0];
			localIntersec = 0;
			localTotalSize = phom.size();
			localTotalRank = phom.rank();
			realIntersec = 0;
			localTotalPhomTypeRank = rankPhomType(phom);
		}

		var intersecRank = localIntersec;
		var totalRank = localTotalRank;
		var totalSize = localTotalSize - realIntersec;
		var totalPhomTypeRank = localTotalPhomTypeRank;

		this.getPhoms = function()
		{
			return phoms;
		};

		this.getPhomsSize = function()
		{
			return phoms.length;
		};

		this.hasNoIntersec = function()
		{
			return intersecRank == 0;
		};

		this.getIntersecRank = function()
		{
			return intersecRank;
		};

		this.getTotalCardSize = function()
		{
			return totalSize;
		};

		this.getTotalCardRank = function()
		{
			return totalRank;
		};

		this.getTotalPhomTypeRank = function()
		{
			return totalPhomTypeRank;
		};

		this.toString = function()
		{
			return phoms.join();
		};

		function rankPhomType(phom)
		{
			switch (phom.getType())
			{
				case SAME_RANK:
					return phom.size();
				case STRAIGHT:
					return 1;
				default:
					throw "Unknown type " + phom.getType();
			}
		}
	}

	function CardSet()
	{
		var elements = {};

		this.add = function(e)
		{
			elements[e] = true;
		};

		this.addAll = function(arr)
		{
			for (var i = 0; i < arr.length; i++)
			{
				elements[arr[i]] = true;
			}
		};

		this.values = function()
		{
			var result = new Array();
			for (key in elements)
			{
				if (elements[key] === true)
				{
					result.push(Card.decodeString(key)[0]);
				}
			}
			return result;
		};
	}

	public class Utils
	{
		public function Utils()
		{
		}
	}
}
